package net.avcompris.binding.json.impl;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;
import static net.avcompris.util.json.JsonUtils.getObjectProperty;
import static net.avcompris.util.json.JsonUtils.getPropertyNames;
import static org.apache.commons.lang3.ClassUtils.isPrimitiveOrWrapper;
import static org.apache.commons.lang3.StringUtils.isBlank;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import net.avcompris.binding.BindConfiguration;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.avcompris.lang.NotImplementedException;

/**
 * JSON-DOM converter.
 * 
 * @author David Andrianavalontsalama
 */
public class DomJsonConverter {

	/**
	 * transform a JSON object into a DOM node.
	 * 
	 * @throws ParserConfigurationException
	 */
	public static Node jsonToNode(final Object json,
			final BindConfiguration configuration)
			throws ParserConfigurationException {

		nonNullArgument(json, "json");
		nonNullArgument(configuration, "configuration");

		final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
				.newInstance();

		// documentBuilderFactory.setNamespaceAware(true);

		final DocumentBuilder documentBuilder = documentBuilderFactory
				.newDocumentBuilder();

		final Document document = documentBuilder.newDocument();

		final DomJsonConverter converter = new DomJsonConverter(document,
				configuration);

		return converter.parseJson(json);
	}

	private final Document document;
	private final BindConfiguration configuration;

	private DomJsonConverter(final Document document,
			final BindConfiguration configuration) {

		this.document = nonNullArgument(document);
		this.configuration = nonNullArgument(configuration);
	}

	private Node parseJson(final Object json) {

		nonNullArgument(json, "json");

		final String rootElementName = "json"; // getKey(json);

		final Element rootElement = createElement(rootElementName);

		document.appendChild(rootElement);

		if (json instanceof JSONObject) {

			parseJson(rootElement, (JSONObject) json);

		} else {

			throw new NotImplementedException("json.class: "
					+ json.getClass().getName());
		}

		return document.getDocumentElement();
	}

	private void parseJson(final Element element, final JSONObject json) {

		nonNullArgument(element, "element");
		nonNullArgument(json, "json");

		for (final String name : getPropertyNames(json)) {

			final Object value = getObjectProperty(json, name);

			if (value == null) {

				// do nothing

				// throw new NotImplementedException(propertyName //
				// +".value == null");

			} else if (value instanceof String || value instanceof Integer
					|| value instanceof Long || value instanceof Boolean) {

				final String s = value.toString();

				if (!configuration.isNodesEmptyAttributes() && isBlank(s)) {

					// do nothing

				} else {

					addAttribute(element, name, s);
				}

			} else if (value instanceof JSONObject) {

				final Element child = document.createElement(name);

				element.appendChild(child);

				parseJson(child, (JSONObject) value);

			} else if (value instanceof JSONArray) {

				for (final Object o : (JSONArray) value) {

					final Element child = createElement(name);

					element.appendChild(child);

					final Class<?> clazz = o.getClass();

					if (JSONObject.class.isAssignableFrom(clazz)) {

						parseJson(child, (JSONObject) o);

					} else if (String.class.equals(clazz)
							|| isPrimitiveOrWrapper(clazz)) {

						final String v = o.toString();

						child.setTextContent(v);
					}
				}

			} else {

				// System.out.println(value.getClass()); // do nothing }
			}
		}
	}

	private void addAttribute(final Element element, final String name,
			final String value) {

		nonNullArgument(element, "element");
		nonNullArgument(name, "name");
		nonNullArgument(value, "value");

		try {

			element.setAttribute(name, value);

		} catch (final DOMException e) {

			throw new DomJsonConversionException("Cannot addAttribute(\""
					+ name + "\", \"" + value + "\")", e);
		}
	}

	private Element createElement(final String name) {

		nonNullArgument(name, "name");

		try {

			return document.createElement(name);

		} catch (final DOMException e) {

			throw new DomJsonConversionException("Cannot createElement(\""
					+ name + "\")", e);
		}
	}
}
